package com.cuit.zuke.system.service.impl;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cuit.zuke.common.exception.GlobalException;
import com.cuit.zuke.common.util.PageUtil;
import com.cuit.zuke.system.dao.SysOssDao;
import com.cuit.zuke.system.entity.SysOssEntity;
import com.cuit.zuke.system.service.SysOssService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @program: zuke
 * @description: oss对象上传服务接口实现类
 * @author: KamTang
 * @create: 2022-04-21 20:29
 **/
@Service
public class SysOssServiceImpl extends ServiceImpl<SysOssDao, SysOssEntity> implements SysOssService {

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucketName;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessKeyId;
    @Value("${spring.cloud.alicloud.secret-key}")
    private String accessKeySecret;

    @Override
    public Map<String, String> getPolicy() {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        // http://kt-emall.oss-cn-chengdu.aliyuncs.com/2020352216990407.jpg
        // host的格式为 bucketname.endpoint
        String host = "https://" + bucketName + "." + endpoint;
        // callbackUrl为 上传回调服务器的URL，请将下面的IP和Port配置为您自己的真实信息。
        // String callbackUrl = "http://88.88.88.88:8888";

        // 用户上传文件时指定的前缀，这里我们使用日期作为文件前缀
        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String dir = format + "/";

        Map<String, String> respMap;
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            // PostObject请求最大可支持的文件大小为5 GB，即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap = new LinkedHashMap<>();
            respMap.put("accessid", accessKeyId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));

        } catch (Exception e) {
            throw new GlobalException(e.getMessage(), 500);
        } finally {
            ossClient.shutdown();
        }
        return respMap;
    }

    @Override
    public PageUtil queryPage(Map<String, Object> params) {
        long currentPage = Long.parseLong((String) params.get("currentPage"));
        long pageSize = Long.parseLong((String) params.get("pageSize"));
        QueryWrapper<SysOssEntity> wrapper = new QueryWrapper<>();
        Page<SysOssEntity> entityPage = new Page<>(currentPage, pageSize);
        Page<SysOssEntity> selectPage = this.baseMapper.selectPage(entityPage, wrapper);
        // 所有数据
        List<SysOssEntity> ossList = selectPage.getRecords();
        // 数据总条数
        int totalCount = (int) selectPage.getTotal();
        return new PageUtil(ossList, totalCount, (int) pageSize, (int) currentPage);
    }

    @Override
    public void createBucket() {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        //判断bucketName是否存在
        if (ossClient.doesBucketExist(bucketName)) {
            throw new RuntimeException(bucketName + "在对象存储的Bucket列表中已经存在");
        }
        // 创建存储空间。
        ossClient.createBucket(bucketName);
        // 关闭OSSClient。
        ossClient.shutdown();
    }

    @Override
    public String upload(MultipartFile file) {
        //上传的地址
        String uploadUrl = null;
        try {
            // 创建OSSClient实例。
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
            //判断bucketName是否存在
            if (!ossClient.doesBucketExist(bucketName)) {
                //创建bucket
                ossClient.createBucket(bucketName);
                //设置bucket的属性
                ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
            }
            //获取上传的文件流
            InputStream inputStream = file.getInputStream();

            //构建日期的文件夹路径  avatar/2020-10-10/文件名
            String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());

            //获取上传文件的全名称
            String original = file.getOriginalFilename();

            //获取UUID  a98059d4-633c-4b55-b310-93c997cf8038
            String fileName = UUID.randomUUID().toString().replaceAll("-", "");

            //获取上传文件的扩展名  meizi.jpg
            String fileType = original.substring(original.lastIndexOf("."));

            //拼接文件名称  a98059d4633c4b55b31093c997cf8038.png
            String newName = fileName + fileType;

            //生成文件夹   avatar/2020-10-10/a98059d4633c4b55b31093c997cf8038.png
            fileName = datePath + "/" + newName;

            //如果想要实现图片预览的效果,一定要设置以下几点
            //1.设置文件的ACL(权限)  要么是公共读,要么是公共读写
            //2.一定要设置文本类型(image/jpg)
            ObjectMetadata objectMetadata = new ObjectMetadata();
            //设置公共读权限
            objectMetadata.setObjectAcl(CannedAccessControlList.PublicRead);
            objectMetadata.setContentType(getContentType(fileType));

            //每次上传得到的名字肯定是不能相同的,在java中如何让每次生成的名字不一样呢?
            //uuid  redis分布式ID 雪花算法   为了更加方便的区分,这边的文件格式yyyy/MM/dd+uuid
            ossClient.putObject(bucketName, fileName, inputStream, objectMetadata);

            // 关闭OSSClient。
            ossClient.shutdown();

            //默认15天不过期
            Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 15);

            //bucket名称  文件名   过期时间
            uploadUrl = ossClient.generatePresignedUrl(bucketName, fileName, expiration).toString();

            //获取url地址
            //uploadUrl = "https://" + bucketName + "." + endPoint + "/" + fileName;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return uploadUrl.substring(0, uploadUrl.indexOf("?"));
        //return uploadUrl;
    }

    @Override
    public void download(String fileName) throws IOException {
        // <yourObjectName>从OSS下载文件时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg。
        String objectName = fileName;

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // 调用ossClient.getObject返回一个OSSObject实例，该实例包含文件内容及文件元信息。
        OSSObject ossObject = ossClient.getObject(bucketName, objectName);
        // 调用ossObject.getObjectContent获取文件输入流，可读取此输入流获取其内容。
        InputStream content = ossObject.getObjectContent();
        if (content != null) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(content));
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }
                System.out.println("\n" + line);
            }
            // 数据读取完成后，获取的流必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
            content.close();
        }
        // 关闭OSSClient。
        ossClient.shutdown();

    }

    @Override
    public void listFile() {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // ossClient.listObjects返回ObjectListing实例，包含此次listObject请求的返回结果。
        ObjectListing objectListing = ossClient.listObjects(bucketName);
        // objectListing.getObjectSummaries获取所有文件的描述信息。
        for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
            System.out.println(" - " + objectSummary.getKey() + "  " +
                    "(size = " + objectSummary.getSize() + ")");
        }

        // 关闭OSSClient。
        ossClient.shutdown();
    }

    @Override
    public void deleteFile(String fileName) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // <yourObjectName>从OSS下载文件时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg。
        ossClient.deleteObject(bucketName, fileName);

        // 关闭OSSClient。
        ossClient.shutdown();
    }

    @Override
    public void deleteBatch(List<String> file) {

        // 截取文件名
        List<String> fileNameList = file.stream().map(fileUrl -> StringUtils.substringAfter(fileUrl, ".com/")).collect(Collectors.toList());
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 删除文件，文件完整路径中不能包含Bucket名称。
            DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(fileNameList).withEncodingType("url"));
            List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
            try {
                for(String obj : deletedObjects) {
                    String deleteObj =  URLDecoder.decode(obj, "UTF-8");
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * Description: 判断OSS服务文件上传时文件的contentType
     *
     * @param fileNameExtension 文件后缀
     * @return String
     */
    public static String getContentType(String fileNameExtension) {
        if (".bmp".equalsIgnoreCase(fileNameExtension)) {
            return "image/bmp";
        }
        if (".gif".equalsIgnoreCase(fileNameExtension)) {
            return "image/gif";
        }
        if (".jpeg".equalsIgnoreCase(fileNameExtension) ||
                ".jpg".equalsIgnoreCase(fileNameExtension) ||
                ".png".equalsIgnoreCase(fileNameExtension)) {
            return "image/jpg";
        }
        if (".html".equalsIgnoreCase(fileNameExtension)) {
            return "text/html";
        }
        if (".txt".equalsIgnoreCase(fileNameExtension)) {
            return "text/plain";
        }
        if (".vsd".equalsIgnoreCase(fileNameExtension)) {
            return "application/vnd.visio";
        }
        if (".pptx".equalsIgnoreCase(fileNameExtension) ||
                ".ppt".equalsIgnoreCase(fileNameExtension)) {
            return "application/vnd.ms-powerpoint";
        }
        if (".docx".equalsIgnoreCase(fileNameExtension) ||
                ".doc".equalsIgnoreCase(fileNameExtension)) {
            return "application/msword";
        }
        if (".xml".equalsIgnoreCase(fileNameExtension)) {
            return "text/xml";
        }
        return "image/jpg";
    }
}
